package main

import (
	"crypto/md5"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"io/ioutil"
	"log"
	"math/big"
	"net"
	"os"
	"time"
)

const (
	RSABitsSize    = 4096
	CACertFilename = "ca.pem"
	CAKeyFilename  = "ca.key"

	ServerCertFilename = "server.pem"
	ServerKeyFilename  = "server.key"

	ClientCertFilename = "client.pem"
	ClientKeyFilename  = "client.key"
)

// GenRsaPK 生成RSA密钥对
func GenRsaPK(size int) *rsa.PrivateKey {
	key, err := rsa.GenerateKey(rand.Reader, size)
	if err != nil {
		panic(err)
	}
	return key
}

// CA 生成CA证书
func CA() {
	key := GenRsaPK(RSABitsSize)

	var caInfo = pkix.Name{
		Country:            []string{"CN"},
		Province:           []string{"SuZhou"},
		Organization:       []string{"SZSCIIT"},
		OrganizationalUnit: []string{"SZSCIIT-SKE"},
		CommonName:         "SKE Server Root CA",
	}
	t := time.Now()
	ca := &x509.Certificate{
		Version:      2, // X509 V3
		SerialNumber: big.NewInt(t.UnixNano()),
		// CA证书的颁发者和使用者信息一致
		Issuer:  caInfo,
		Subject: caInfo,
		// 证书时效
		NotBefore: t,
		NotAfter:  t.AddDate(10, 0, 0),
		// 签名算法
		SignatureAlgorithm: x509.SHA1WithRSA,
		// 证书用途
		KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign | x509.KeyUsageDataEncipherment,
		ExtKeyUsage: []x509.ExtKeyUsage{
			x509.ExtKeyUsageClientAuth,
			x509.ExtKeyUsageServerAuth,
		},
		// 表示该证书为CA证书
		BasicConstraintsValid: true,
		IsCA:                  true,
		// 生成使用者秘钥标识-> nil则自动生成
		SubjectKeyId: md5.New().Sum([]byte(t.String())),
	}
	cb, err := x509.CreateCertificate(rand.Reader, ca, ca, &key.PublicKey, key)
	if err != nil {
		panic(err)
	}

	save(CACertFilename, &pem.Block{
		Type:  "CERTIFICATE",
		Bytes: cb,
	})

	save(CAKeyFilename, &pem.Block{
		Type:  "RSA PRIVATE KEY",
		Bytes: x509.MarshalPKCS1PrivateKey(key),
	})
}

// Server 颁发服务器证书
func Server(ca *x509.Certificate, caKey *rsa.PrivateKey) {
	key := GenRsaPK(RSABitsSize)
	var serverInfo = pkix.Name{
		Country:            []string{"CN"},
		Province:           []string{"SuZhou"},
		Organization:       []string{"SZSCIIT"},
		OrganizationalUnit: []string{"SZSCIIT-SKE"},
		CommonName:         "SKE Server",
	}
	t := time.Now()
	serverReq := &x509.Certificate{
		Version:      2,
		SerialNumber: big.NewInt(t.UnixNano()),
		Subject:      serverInfo,
		NotBefore:    t,
		NotAfter:     t.AddDate(3, 0, 0),
		KeyUsage:     x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
		ExtKeyUsage: []x509.ExtKeyUsage{
			x509.ExtKeyUsageServerAuth,
		},
		// 可选DNS名称, 浏览器 GRPC HTTP都是兼容的
		DNSNames: []string{
			"localhost",
			"*.ldsec.co",
		},
		IPAddresses: []net.IP{
			net.ParseIP("127.0.0.1"),
			net.ParseIP("192.168.255.1"),
			net.ParseIP("192.168.255.2"),
			net.ParseIP("192.168.255.3"),
			net.ParseIP("192.168.255.4"),
			net.ParseIP("192.168.255.5"),
		},
		// 授权者表示, 表示上一级证书的SubjectKeyId, 用于构成证书链
		AuthorityKeyId: ca.SubjectKeyId,
	}

	serverCert, err := x509.CreateCertificate(rand.Reader, serverReq, ca, &key.PublicKey, caKey)
	if err != nil {
		panic(err)
	}
	save(ServerKeyFilename, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
	save(ServerCertFilename, &pem.Block{Type: "CERTIFICATE", Bytes: serverCert})
}

// Client 颁发客户端证书
func Client(ca *x509.Certificate, caKey *rsa.PrivateKey) {
	key := GenRsaPK(RSABitsSize)
	var clientInfo = pkix.Name{
		Country:            []string{"CN"},
		Province:           []string{"SuZhou"},
		Organization:       []string{"SZSCIIT"},
		OrganizationalUnit: []string{"SZSCIIT-SKE"},
		CommonName:         "SKE Agent",
	}
	t := time.Now()
	clientReq := &x509.Certificate{
		Version:      2,
		SerialNumber: big.NewInt(t.UnixNano()),
		Subject:      clientInfo,
		NotBefore:    t,
		NotAfter:     t.AddDate(3, 0, 0),
		KeyUsage:     x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
		ExtKeyUsage: []x509.ExtKeyUsage{
			x509.ExtKeyUsageClientAuth,
		},
		AuthorityKeyId: ca.SubjectKeyId,
	}

	clientCert, err := x509.CreateCertificate(rand.Reader, clientReq, ca, &key.PublicKey, caKey)
	if err != nil {
		panic(err)
	}
	save(ClientKeyFilename, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
	save(ClientCertFilename, &pem.Block{Type: "CERTIFICATE", Bytes: clientCert})
}

func main() {

	CA()

	// parse CA cert
	file, err := ioutil.ReadFile(CACertFilename)
	if err != nil {
		panic(err)
	}
	caData, _ := pem.Decode(file)
	ca, err := x509.ParseCertificate(caData.Bytes)
	if err != nil {
		panic(err)
	}

	// parse CA KEY
	file2, err := ioutil.ReadFile(CAKeyFilename)
	if err != nil {
		panic(err)
	}
	keyData, _ := pem.Decode(file2)
	caKey, err := x509.ParsePKCS1PrivateKey(keyData.Bytes)
	if err != nil {
		panic(err)
	}

	Server(ca, caKey)

	Client(ca, caKey)

}

func save(filename string, block *pem.Block) {
	fp, err := os.Create(filename)
	if err != nil {
		panic(err)
	}
	if err := pem.Encode(fp, block); err != nil {
		panic(err)
	}
	log.Printf("%s save success!", filename)
}

